import { CronJob } from "cron";
import { join, parse } from 'path';
import * as fs from 'fs';
import CronConfig from '/Config/Cron';

/**
* * * * * *
- - - - - -
| | | | | |
| | | | | +--- 星期几 (0 - 7) (0 或 7 表示周日)
| | | | +----- 月份 (1 - 12)
| | | +------- 日期 (1 - 31)
| | +--------- 小时 (0 - 23)
| +----------- 分钟 (0 - 59)
+------------- 秒 (0 - 59)
 */

/**存储当前全部的缓存任务的 */
const CronCache = new Map<string, CronJob<() => void, null>>();

/**
 * 自身的CronJob对象
 */
export type cronJob = CronJob<() => void, null>;

export interface CronControlType {
    /**
     * 启动一个任务
     * @param taskName 任务名称
     */
    start(taskName: string): boolean;
    /**
     * 停止一个任务
     * @param taskName 任务名称
     */
    stop(taskName: string): boolean;

    /**
     * 检查一个任务是否正在运行
     * @param taskName 任务名称
     */
    isRun(taskName: string): boolean | undefined;

    /**
     * 获取一个任务的上下文对象
     * @param taskName 任务名称
     */
    context(taskName: string): Record<string, any> | null;

    /**
     * 获取指定任务的CronJob对象
     * @param taskName 任务名称
     */
    getCronJob(taskName: string): cronJob | undefined;

}

const CronControl = {

    start(taskName: string) {
        if (!CronCache.has(taskName)) {
            logger.error(`[定时任务] ${taskName}任务不存在，无法启动它！`);
            return false;
        }
        CronCache.get(taskName)?.start();
        return true;
    },

    stop(taskName: string) {
        if (!CronCache.has(taskName)) {
            logger.error(`[定时任务] ${taskName}任务不存在，无法停止！`);
            return false;
        }
        CronCache.get(taskName)?.stop();
        return true;
    },

    isRun(taskName: string) {
        if (!CronCache.has(taskName)) {
            logger.error(`[定时任务] ${taskName}任务不存在，无法检查它！`);
            return undefined;
        }
        return CronCache.get(taskName)?.running;
    },
    context(taskName: string) {
        if (!CronCache.has(taskName)) {
            logger.error(`[定时任务] ${taskName}任务不存在，无法获取它！`);
            return null;
        }
        return CronCache.get(taskName)?.context;
    },
    getCronJob(taskName: string) {
        if (!CronCache.has(taskName)) {
            logger.error(`[定时任务] ${taskName}任务不存在，无法获取它！`);
            return undefined;
        }
        return CronCache.get(taskName);
    }
} as CronControlType;


export class Cron {

    /**
     * 注册一个定时任务
     * @param cronTime cron表达式
     * @param start 是否立即启动 默认为true
     * @param runOnInit 初始化后立即触发 onTick 函数。默认值为 false。
     * @returns 
     */
    static from(cronTime: string, start: boolean = true, runOnInit: boolean = false) {
        /**
         * key 当前方法名字
         */
        return (target: any, key: string, descriptor: PropertyDescriptor) => {
            const originalMethod = descriptor.value; // 保存原方法
            descriptor.value = function (cc: CronControlType, newObjClass: any, filePath: string) {
                const cronaddress = filePath.replace(/^.+\\schedule\\(.+)$/, '$1');
                const cronIndex = join(cronaddress, key).replace(/\\/g, '/');
                const JobObj = CronJob.from({
                    cronTime, // 表达式
                    onTick() {
                        if (process.env.NODE_ENV === 'development') {
                            CronConfig.isLogRunDev && logger.info(`[定时任务] ${cronIndex} 任务开始执行！下次执行：${this.nextDate().toString()}`);
                        } else {
                            CronConfig.isLogRun && logger.info(`[定时任务] ${cronIndex} 任务开始执行！下次执行：${this.nextDate().toString()}`);
                        }
                        try {
                            originalMethod.call(newObjClass, cc, this); // 调用原方法
                        } catch (error) {
                            logger.error(`[定时任务] ${cronIndex} 任务执行出错！`, error);
                            this.stop();// 出错停止任务
                        }
                    }, // 任务执行函数
                    onComplete: () => { logger.info(`[定时任务] ${cronIndex} 任务执行完成，已停止！`); }, // 任务完成后的回调函数（可选）
                    start, // 是否立即启动任务
                    timeZone: 'Asia/Shanghai', // 时区
                    context: null, // 上下文对象（可选）
                    runOnInit, // [可选] - 在初始化后立即触发 onTick 函数。默认值为 false。
                });
                CronCache.set(cronIndex, JobObj);
            };
            // 给方法添加一个标志属性，表示它被装饰了
            descriptor.value._decoratedWithMyDecorator = true;
            return descriptor;
        }
    }
}



/**
 * 递归遍历控制器文件夹
 * @param {String} path 路径
 */
function eachController(path: string, callback: ((path: string, fileName: string) => any)) {
    const fsList: Array<fs.Dirent> = fs.readdirSync(path, { withFileTypes: true });
    for (const file of fsList) {
        if (!file.isFile()) {
            // 文件夹
            let NewPath = path + '/' + file.name;
            eachController(NewPath, callback);//继续递归
        } else if (/.*\.(js|ts)$/i.test(file.name)) {
            // ts|js文件
            let fileName = parse(file.name).name;//解析移除后缀
            callback(path, fileName);//回调函数
        }
    }
}




export default function () {
    const initPath = join(__dirname, '../App/schedule/');
    eachController(initPath, (path, fileName) => {
        const filePath = join(path, fileName);
        if (!fs.existsSync(`${filePath}.ts`) && !fs.existsSync(`${filePath}.js`)) {
            logger.warn(`计划任务[${filePath}]不存在`);
            return false;
        }
        const { default: newObj } = require(filePath);
        if (typeof newObj?.constructor != 'function') {
            logger.warn(`计划任务[${filePath}]不是一个类`);
            return false;
        };
        const newObjClass = new newObj(); // 实例化类
        // 获取类的方法列表
        const methodNames = Object.getOwnPropertyNames(Object.getPrototypeOf(newObjClass)).filter(name => name != 'constructor');//返回类的所有方法(不包含 constructor )
        // logger.info(`计划任务[${filePath}]方法列表：${methodNames}`);
        for (const methodName of methodNames) {
            const method = newObjClass[methodName];
            if (typeof method !== 'function') {
                logger.error(`计划任务[${filePath}]下的[${methodName}]不是一个函数`);
                continue; // 忽略非函数属性
            }
            if (!method._decoratedWithMyDecorator) continue; // 忽略未装饰的函数
            method.call(newObjClass, CronControl, newObjClass, filePath);
        }
    });
}